﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
using GimmeRainbow.Abstract;

namespace GimmeRainbow.Concrete
{
    public class SaltedHasher : ISaltedHasherAsync
    {
        # region privateMemebers
        string _outputDirectory = ConfigurationManager.AppSettings["OutputDirectory"];
        string _logFile = ConfigurationManager.AppSettings["LogFile"];
        string _perHashFile = ConfigurationManager.AppSettings["PerHashFile"];
        string _enableLogging = ConfigurationManager.AppSettings["EnableLogging"];
        string _generateHashWithoutSalt = ConfigurationManager.AppSettings["GenerateHashWithoutSalt"];
        string _numberOfRepeatations = ConfigurationManager.AppSettings["NumberOfRepeatations"];

        # endregion

        #region Implementation of ISaltedHasherAsync

        public void GenerateSaltedHashAsync(string filePath, string saltFilePath, HelperClass.HashType hashType, CancellationToken cancellationToken, TaskScheduler taskScheduler)
        {
            
            
            // open the files
            try
            {
                var dictionaryWords = File.ReadAllLines(filePath);
                var salts = File.ReadAllLines(saltFilePath);
                

                // now we got the salts and file
                // make the settings object
                foreach (var hash in hashType.ToString().Split(','))
                {
                    HelperClass.HashType hashTypeValue;
                    if (!Enum.TryParse(hash, out hashTypeValue))
                        throw new InvalidDataException();
                    var hashResult =
                        new TransformBlock<IEnumerable<string>, IEnumerable<string>>(enumerable =>
                                                                                         {
                                                                                             try
                                                                                             {
                                                                                                 // return enumerable =>
                                                                                                 return MakeSaltAsync(
                                                                                                     dictionaryWords,
                                                                                                     salts,
                                                                                                     hashTypeValue,
                                                                                                     cancellationToken,
                                                                                                     taskScheduler);
                                                                                             }
                                                                                             catch (
                                                                                                 OperationCanceledException
                                                                                                 )
                                                                                             {
                                                                                                 return
                                                                                                     Enumerable.Empty
                                                                                                         <string>();
                                                                                             }
                    
                                                                                         });

                    
                    // now we are done with results
                    // now save the file
                    var actionHashToString = new ActionBlock<IEnumerable<string>>(enumerable => SaveHash(enumerable,
                                                                                                         cancellationToken,
                                                                                                         taskScheduler));

                    hashResult.LinkTo(actionHashToString);

                    hashResult.Post(dictionaryWords);

                    /*
                    var bt = new BatchBlock<string>(10).LinkTo(actionHashToString);

                    

                    var batchBlock = new BatchBlock<IEnumerable<string>>(10000,
                                                                         new GroupingDataflowBlockOptions
                                                                             {
                                                                                 CancellationToken = cancellationToken,
                                                                                 TaskScheduler = taskScheduler
                                                                             });





                    var actionBlock = new ActionBlock<IEnumerable<IEnumerable<string>>>(enumerable =>
                                                                               {

                                                                                   for (var i = 0;
                                                                                        i < enumerable.Count();
                                                                                        i++)
                                                                                   {
                                                                                       //File.AppendAllText(@"D:\Text.log",
                                                                                       //  enumerable.ElementAt(i));
                                                                                       MessageBox.Show(
                                                                                           enumerable.Join(","));

                                                                                   }
                                                                               });


                    hashResult.LinkTo(batchBlock);
                        
                    // link the batch
                    batchBlock.LinkTo(actionBlock);

                    batchBlock.LinkTo(new ActionBlock<IEnumerable<string>[]>(en => {  }));
                    */
                }

                //var result = await MakeSaltAsync(dictionaryWords, salts, hashType);
                //return result;

            }
            catch(Exception exception)
            {
                //Log(exception);
                throw;
            }
        }

        private void SaveHash(IEnumerable<string> hashes, CancellationToken cancellationToken, TaskScheduler taskScheduler)
        {
            // now in here we have all the hashes
            // but what we want is to limit a max to 10000, and then save in batch
            var batchBlock = new BatchBlock<string>(10,
                                                    new GroupingDataflowBlockOptions
                                                        {
                                                            CancellationToken = cancellationToken,
                                                            TaskScheduler = taskScheduler
                                                        });

            // create a action block
            var actionBlock = new ActionBlock<IEnumerable<string>>(enumerable =>
                                                                       { /*
                                                                           using(var fs = new FileStream(@"D:\Shit.log", FileMode.Append))
                                                                           {
                                                                               for (var i = 0;
                                                                                    i < enumerable.Count();
                                                                                    i++)
                                                                               {
                                                                                   // File.AppendAllText(@"D:\Text.log", enumerable.ElementAt(i));
                                                                                   var bytes =
                                                                                       Encoding.UTF8.GetBytes(
                                                                                           enumerable.ElementAt(i));
                                                                                   
                                                                                   fs.Write(bytes, 0, bytes.Length);
                                                                               }
                                                                           }
                                                                          */
                                                                           for (var i = 0;
                                                                                   i < enumerable.Count();
                                                                                   i++)
                                                                           {
                                                                               MessageBox.Show(enumerable.ElementAt(i));
                                                                           }
                                                                       });

            batchBlock.LinkTo(actionBlock);

            // now all the linking is set, post it
            var enumerableInner = hashes as string[] ?? hashes.ToArray();
            for (var i = 0; i < enumerableInner.LongCount(); i++)
            {
                batchBlock.Post(enumerableInner.ElementAt(i));
            }


        }

        IEnumerable<string> MakeSaltAsync(ICollection<string> dictionaryWords, IEnumerable<string> salts, HelperClass.HashType hashType, CancellationToken cancellationToken, TaskScheduler taskScheduler)
        {
            // find the hashType
            byte[] byteArray = new byte[] {};
            byte[] hashData = new byte[]{};
            StringBuilder stringBuilder = new StringBuilder();

            IList<string> list = new List<string>();
            if (hashType == HelperClass.HashType.Md5) 
            {
                for (var i = 0; i < salts.Count(); i++)
                {
                    for (var j = 0; j < dictionaryWords.Count; j++)
                    {
                        using (MD5 md5 = MD5.Create())
                        {
                            byteArray = Encoding.UTF8.GetBytes(salts.ElementAt(i));
                            byteArray = byteArray.Concat(Encoding.UTF8.GetBytes(dictionaryWords.ElementAt(j))).ToArray();
                            hashData = md5.ComputeHash(byteArray);
                        }
                        stringBuilder.Clear();
                        for (var k = 0; k < hashData.Length; k++)
                            stringBuilder.Append(hashData[k].ToString("x2"));
                        
                        list.Add(stringBuilder.ToString());
                    }
                }
            }
            return list;

        }

        public IEnumerable<string> GenerateSaltedHashAsync(string filePath, string saltFilePath, HelperClass.HashType hashType)
        {
            throw new NotImplementedException();
        }

        public void GenerateSaltedHashAsync(string filePath, Stream saltFile, HelperClass.HashType hashType)
        {
            throw new System.NotImplementedException();
        }

        public void GenerateSaltedHashAsync(Stream stream, HelperClass.HashType hashType)
        {
            throw new System.NotImplementedException();
        }

        public void GenerateSaltedHashAsync(Stream stream, Stream saltStream, HelperClass.HashType hashType)
        {
            throw new System.NotImplementedException();
        }

        public void GenerateSaltedHashAsync(IEnumerable<string> filePaths, HelperClass.HashType hashType)
        {
            throw new System.NotImplementedException();
        }

        public void GenerateSaltedHashAsync(IEnumerable<string> filePaths, string saltFilePath, HelperClass.HashType hashType)
        {
            throw new System.NotImplementedException();
        }

        public void GenerateSaltedHashAsync(IEnumerable<string> filePaths, Stream saltStream, HelperClass.HashType hashType)
        {
            throw new System.NotImplementedException();
        }

        public void GenerateSaltedHashAsync(IEnumerable<Stream> fStreams, string saltFilePath, HelperClass.HashType hashType)
        {
            throw new System.NotImplementedException();
        }

        public void GenerateSaltedHashAsync(IEnumerable<Stream> fStreams, Stream saltFilePath, HelperClass.HashType hashType)
        {
            throw new System.NotImplementedException();
        }

        #endregion
    }
}
